Hola hoy día vamos a tener unos temas que nos van a ayudar a manejar mejor nuestro código en Ruby, también estos temas son parte de la programación orientada a objetos.
Módulos
Un módulo nos permite agrupar generalmente clases y funciones o subrutinas, para poder utilizarlos posteriormente, se declara con module, el nombre de módulo debe empezar con maúsculas y se termina con un end, en el medio debe estar nuestro código:
irb(main):350:0> module Herramientas
irb(main):351:1> VERSION = 1.1
irb(main):352:1> def Herramientas.sumar a, b
irb(main):353:2> a + b
irb(main):354:2> end
irb(main):355:1> def self.restar a, b
irb(main):356:2> return a - b
irb(main):357:2> end
irb(main):358:1> end
=> nil
Bueno en este ejemplo vemos en la linea 350 que se ha declarado el módulo "Herramientas" con el uso de "module", luego en la linea 351 se ha creado una constante VERSION, luego vemos que se ha creado un método "sumar", pero observamos que esta escrito como Herramientas.sumar, osea tiene el nombre del módulo mas un punto y seguidamente del método, esto indica que ese método es usable directamente desde el módulo; Acto seguido vemos en la linea 355 que se crea el método restar, pero observamos que hay un "self." ese self es como un reemplazo de Herramientas (para no tener que escribir el nombre del módulo) y es casi lo mismo que haber escrito "Herramientas.restar" pero hay que tener cuidado ya que self cambia de acuerdo al contexto del escenario, por lo que solo funcionará cuando lo llamemos dentro de su módulo, vemos en la línea 356 el uso de "return" pero es opcional, puede haber quedado simplemente como a - b.
Para usar el módulo directamente, hacemos lo siguiente:
irb(main):359:0> Herramientas.sumar 5, 2
=> 7
irb(main):360:0> Herramientas.restar 5, 2
=> 3
irb(main):361:0> Herramientas::VERSION
=> 1.1
irb(main):362:0> Herramientas::restar 5, 2
=> 3
Vemos que estamos usando los métodos sumar y restar directamente desde el módulo Herramientas, pero en la línea 361 vemos un operador nuevo, los cuatro puntos :: este operador nos sirve para accesar a las constantes, métodos, clases y otros módulos. Pero solo podemos hacer eso desde módulos o clases.
Herencia
En Ruby, el creador de este lenguaje ha preferido, por temas de diseño y practicidad, utilizar la herencia simple y usar MixIN para reemplazar la herencia múltiple, nos asegura que nos quita un dolor de cabeza
Para realizar la herencia simplemente se usa el símbolo < (menor que) y este símbolo " < " se debe usar al declarar una nueva clase:
irb(main):367:0> class Lobo
irb(main):368:1> def aullar
irb(main):369:2> "Auuuuuu"
irb(main):370:2> end
irb(main):371:1> end
=> nil
irb(main):372:0> class Perro < Lobo
irb(main):373:1> def ladrar
irb(main):374:2> "Guau!"
irb(main):375:2> end
irb(main):376:1> end
=> nil
irb(main):377:0> chihuahua = Perro.new
=> #<Perro:0x000000044d8e70>
irb(main):378:0> chihuahua.ladrar
=> "Guau!"
irb(main):379:0> chihuahua.aullar
=> "Auuuuuu"
Como podemos ver, se ha creado una clase Lobo que tiene el método aullar, luego en la línea 372, hemos creado la clase Perro, pero esta "hereda" de la clase Lobo, la clase perro tiene solamente el método ladrar, pero al heredar de su ancestro Lobo, entonces tambien va a poder "aullar", osea utilizar el método aullar de la clase Lobo de la cual ha heredado, por eso hemos instanciado un Chihuaha de la clase Perro, pero este Chihuahua puede ladrar y también puede aullar (por herencia, se puede decir que Perro es una subclase de Lobo).
irb(main):381:0> chihuahua.class
=> Perro
irb(main):384:0> chihuahua.instance_of? Perro
=> true
irb(main):385:0> chihuahua.instance_of? Lobo
=> false
irb(main):386:0> chihuahua.kind_of? Lobo
=> true
irb(main):387:0> chihuahua.kind_of? Perro
=> true
irb(main):388:0> chihuahua.is_a? Perro
=> true
irb(main):389:0> chihuahua.is_a? Lobo
=> true
irb(main):433:0> Perro.superclass
=> Lobo
Como se puede ver, aqui hay unos métodos interesantes, en la linea 381 preguntamos de que clase es el chihuahua y nos dice que es un Perro, luego en la 384 le preguntamos si este chihuahua ¿es una instancia de Perro? (instance_of) y nos dice que si, le preguntamos en la 385 si ¿es una instancia de Lobo? y nos dice que no, pero en la 386 le preguntamos si el chihuahua ¿es una especie o tipo de lobo? (kind_of) y nos dice que sí, ya que el Perro es una subclase de Lobo, así que el Perro es una especie de Lobo (por increible que sea), ahora en las líneas 388 y 389 el método is_a? es un sinónimo de kind_of?, es decir son lo mismo; también en la línea 433 el superclass devuelve cual es la clase padre de la clase Perro.
MixIN (reemplazo de herencia múltiple)
Bueno ahora bien lo bueno, el MixIN es para poder utilizar otros módulos (que pueden contener otras clases y/o modulos) dentro de una clase o módulo, de tal manera que se reutiliza funcionalidad. Ahora veamos el módulo anterior:
irb(main):395:0> module Herramientas
irb(main):396:1> VERSION = 1.1
irb(main):397:1> def Herramientas.sumar a, b
irb(main):398:2> a + b
irb(main):399:2> end
irb(main):400:1> def self.restar a, b
irb(main):401:2> return a - b
irb(main):402:2> end
irb(main):403:1> def multiplicar
irb(main):404:2> "no implementado"
irb(main):405:2> end
irb(main):406:1> end
=> nil
Hemos añadido el método multiplicar al módulo Herramientas (línea 403) pero observamos que simplemente es "def multiplicar" y no es "def self.multiplicar" ni "def Herramientas.multiplicar", lo cual significa que ese método solo se puede usar en una instancia; ¿Pero se puede crear una instancia de un módulo? la respuesta es no!, entonces viene otra pregunta ¿en que momento se puede usar ese método si no se puede crear instancias de un módulo? o de otra manera ¿que sentido tiene definir métodos de instancia en el módulo si no se puede crear una instancia del módulo?, pues la solución es que solamente se puede si la "mezclamos", es decir, si realizamos el MixIN:
irb(main):413:0> class Calculadora
irb(main):414:1> extend Herramientas
irb(main):415:1> def que_realiza?
irb(main):416:2> "operaciones"
irb(main):417:2> end
irb(main):418:1> end
=> nil
irb(main):211:0> Calculadora.multiplicar
=> "no implementado"
Con el extend de la línea 414, nos sirve para que la clase Calculadora (no las instancias) pueda obtener los métodos de instancia del módulo (osea solo el "def multiplicar"), y en la línea 211 se ve que llamamos a ese método "multiplicar"; Pero desde la clase no podemos acceder directamente a la constante VERSION, y en una instancia de Calculadora tampoco podremos acceder a nada del módulo Herramientas, entonces para ello realizamos el MixIN:
Haciendo MixIN
irb(main):221:0> class Calculadora
irb(main):222:1> extend Herramientas
irb(main):223:1> include Herramientas
irb(main):224:1> def que_realiza?
irb(main):225:2> "operaciones"
irb(main):226:2> end
irb(main):227:1> end
=> nil
irb(main):229:0> Calculadora::VERSION
=> 1.1
Ahora que se ha realizado el MixIN, la clase Calculadora ya puede acceder a la constante VERSION del módulo Herramientas, pero no solamente eso, sino que ahora las instancias de Calculadora pueden acceder:
irb(main):233:0> calc = Calculadora.new
=> #<Calculadora:0x2dd1c28>
irb(main):237:0> calc.multiplicar
=> "no implementado"
irb(main):238:0> calc.que_realiza?
=> "operaciones"
Ahora vamos a crear un módulo especial
irb(main):239:0> module Plus
irb(main):240:1> def graficos_avanzados
irb(main):241:2> true
irb(main):242:2> end
irb(main):243:1> end
=> nil
Y vamos a crear una clase CalculadoraCientifica que heredará de Calculadora y hará un MixIN con el módulo Plus (hay que considerar que Calculadora tiene MixIN con Herramientas)
irb(main):261:0> class CalculadoraCientifica < Calculadora
irb(main):262:1> include Plus
irb(main):263:1> def que_realiza?
irb(main):264:2> "realiza: #{super}, graficos"
irb(main):265:2> end
irb(main):266:1> end
=> nil
irb(main):267:0> casio = CalculadoraCientifica.new
=> #<CalculadoraCientifica:0x30645d8>
irb(main):268:0> casio.que_realiza?
=> "realiza: operaciones, graficos"
irb(main):269:0> casio.graficos_avanzados
=> true
irb(main):270:0> casio.multiplicar
=> "no implementado"
Como se puede ve en la linea 262 hemos hecho un MixIN con Plus, y hemos obtenidos las funcionalidades de Plus (los métodos de instancia), ahora en la línea 264 se ve que estamos devolviendo una cadena de texto o string, y usamos el #{} ("michi" o numeral y llaves) que sirven para reemplazar por alguna variable en su interior, y en el interior usamos la palabra clave "super" que significa que va a utilizar el método "que_realiza?" pero de la clase padre Calculadora, luego como esta devuelve un string "operaciones" y reemplazando obtenemos "realiza: operaciones, graficos"; luego también heredamos los métodos MixIN de Calculadora del módulo Herramientas como el método de instancia "multiplicar".
Ahora también podemos usar el método:
irb(main):271:0> CalculadoraCientifica.ancestors
=> [CalculadoraCientifica, Plus, Calculadora, Herramientas, Object, Kernel, BasicObject]
Que nos devuelve un array conteniendo las clases y/o módulos que "arman" a la clase CalculadoraCientifica, empieza por la misma clase, luego el módulo Plus, luego la clase Calculadora y el módulo Herramientas, las otras clases Object, Kernel, BasicObject, son añadidas tanto por el lenguaje Ruby como por el IRB.
Es importante que primero Ruby buscará los métodos en su propia clase CalculadoraCientifica, si no lo encuentra proseguirá en buscar en Plus, si no lo encuentra proseguirá en buscar en Calculadora y luego en Herramientas.
Para las clases, el último método sobreescribe al primero, como se vió en la línea 264, se volvio a sobreescribir el método que_realiza? se debe considerar como concepto vertical de sobreescritura de métodos en herencias y MixIN.
Muy bien, culminamos este interesante capítulo, hasta aqui vamos con el capitulo 17 y sugiero que lo lean varias veces y practiquen ya que este tema es un pilar para los diversos frameworks y utilidades escritas en Ruby, el siguiente capítulo es el número 18, cualquier duda pueden comentar y sera absuelto, no tienes que registrarte ni realizar nada adicional para comentar, soy su amigo!.